package edu.hawaii.halealohacli.command;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Formatter;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import javax.xml.datatype.XMLGregorianCalendar;
import org.wattdepot.client.ResourceNotFoundException;
import org.wattdepot.client.WattDepotClient;
import org.wattdepot.util.tstamp.Tstamp;

/**
 * Uses the energy consumption on an earlier day as the baseline of hourly power consumption for a
 * given source. Other commands can then compare current power consumption to this baseline.
 * <p>
 * Usage: set-baseline [tower | lounge] [date]
 * <p>
 * This command defines [date] as the "baseline" day for [tower | lounge]. [date] is an optional
 * argument in YYYY-MM-DD format, and must be specified as yesterday or earlier. If not specified,
 * and defaults to yesterday. When this command is executed, it obtains and saves the amount of
 * energy used during each of the 24 hours of that day for the given tower or lounge. These 24
 * values define the baseline power for that tower or lounge for that one hour time interval. For
 * example, if lounge Ilima-A used 100 kWh of energy during the hour 6am-7am, then the baseline
 * power during the interval 6am - 7am for Ilima-A is 100 kW.
 * 
 * @author Jeffrey M. Beck
 * @author Zach Tomaszewski
 * 
 */
public class SetBaseline implements Command {

  /** The WattDepot client to obtain energy values from. **/
  private WattDepotClient client;
  /** The tower or lounge name. **/
  private String sourceName;
  /** Flag for valid args. **/
  private boolean valid;
  /** The date today. **/
  private Date date = new Date();
  /** Todays date as a command line string. **/
  private String dateAsString = null;
  /** The start of an hour for retrieving hourly energy values. **/
  private XMLGregorianCalendar startOfHour;
  /** The end of an hour for retrieving hourly energy values. **/
  private XMLGregorianCalendar endOfHour;
  /** Todays date, used for validating user specified date and calculating the default date. **/
  private XMLGregorianCalendar today;
  /** User specified date. **/
  private XMLGregorianCalendar startDate;

  // Because of the current reflection-based design in which a new instance of
  // a command is constructed each time it is executed, this map has to be
  // static so that all different instances contribute to the same map.
  // Since it is static, it made sense to make its accessor methods static too
  // so it could be accessed either directly or through an instance.
  private static Map<String, double[]> baselines = new HashMap<String, double[]>();

  /**
   * Expects an argument array containing the name of the source to set energy values for and
   * (optionally) a date.
   * 
   * @param args A string array containing the two arguments.
   * @param client The WattDepot client to collect this data from.
   */
  @Override
  public void initialize(String[] args, WattDepotClient client) {
    this.client = client;
    valid = (args != null && args.length > 0 && args.length < 3);

    if (valid) {
      sourceName = args[0];
      today = Tstamp.makeTimestamp(date.getTime());

      if (args.length > 1) {
        // user specified a date
        dateAsString = args[1];
  
        /** Make sure optional arg is YESTERDAY or earlier **/
  
        try {
          date = new Date();
  
          today.setHour(0);
          today.setMinute(0);
          today.setSecond(0);
          today.setMillisecond(0);
          // System.out.println("today " + today);
  
          startDate = Tstamp.makeTimestamp(dateAsString);
          startDate.setHour(0);
          startDate.setMinute(0);
          startDate.setSecond(0);
          startDate.setMillisecond(0);
          // System.out.println("startDate " + startDate);
  
          if (Tstamp.lessThan(startDate, today)) {
            // ok = false;
            // error = "The start date must be yesterday or earlier.";
  
            startOfHour = Tstamp.makeTimestamp(dateAsString);
            endOfHour = Tstamp.makeTimestamp(dateAsString);
          }
          else {
            valid = false;
          }
  
        }
        catch (Exception e) {
          valid = false;
        }
      }
      else {
        // Specify yesterday as the date because no date given on command line.
        startOfHour = Tstamp.incrementDays(today, -1);
        endOfHour = Tstamp.incrementDays(today, -1);
      }
    }
  }

  /**
   * Prints the returned value from getResults to the console.
   */
  @Override
  public void execute() {
    System.out.println(getResults());
  }

  /**
   * Finds the amount of power used each hour and saves it. Displays the values being stored to the
   * user in a table format.
   * 
   * @return A string displaying the results, or the error that occurred.
   */
  public String getResults() {

    if (client == null || !client.isHealthy()) {
      return "Invalid WattDepot client.";
    }
    if (!valid) {
      return "Invalid arguments for " + getCommandName() + ".\n" + getCommandUsage();
    }

    double[] data = new double[24];
    double energy = 0;
    int hour = 0;

    startOfHour.setTime(0, 0, 0);
    endOfHour.setTime(0, 0, 0);

    /** Query server and set hourly data values. **/
    for (hour = 0; hour < 24; hour++) {
      startOfHour.setTime(hour, 0, 0);
      endOfHour.setTime(hour + 1, 0, 0);

      try {
        energy = client.getEnergyConsumed(sourceName, startOfHour, endOfHour, 0);
      }
      catch (ResourceNotFoundException e) {
        return sourceName + " is not a valid source name.";
      }
      catch (Exception e) {
        return ("Encountered a problem getting WattDepot energy values.\n");
      }
      data[hour] = energy / 1000;
    }

    // set baseline values
    setBaseline(sourceName, data);

    StringBuilder sb = new StringBuilder();
    Formatter formatter = new Formatter(sb, Locale.US);

    // get baseline values
    double[] bl = getBaseline(sourceName);

    // print table header
    if (dateAsString == null) {
      GregorianCalendar calendar = startOfHour.toGregorianCalendar();
      SimpleDateFormat format = new SimpleDateFormat("yyyy-M-dd", Locale.US);
      formatter.format("Setting baseline values for %s on %s \n", sourceName,
          format.format(calendar.getTime()));
    }
    else {
      formatter.format("Setting baseline values for %s on %s \n", sourceName, dateAsString);
    }
    formatter.format("%-5s %-5s \n", "hour", "energy");

    // print table rows
    for (int i = 0; i < 24; i++) {
      formatter.format("%-5s %.1f kWh \n", i, bl[i]);
    }
    return formatter.toString();

  }

  @Override
  public String getCommandName() {
    return "set-baseline";
  }

  @Override
  public String getCommandUsage() {
    return getCommandName()
        + " [tower | lounge] [date]\nSaves the amount of energy used each hour for"
        + " the date specified.\nThis data then serves as a baseline for comparison by"
        + " monitor-goal.\nIf no date is specified, yesterday's date is used.\n\n";
  }

  /**
   * Returns the previously-computed hourly baselines for the given source. This is an array of 24
   * hour power consumption averages. Index 0 corresponds to the hour between midnight to 1am, index
   * 1 is 1am to 2am, and so on.
   * 
   * @param source The source (such as a tower or lounge) to get data for
   * @return An array of hourly power consumption averages
   * 
   * @throws IllegalArgumentException if there is no baseline data set for the given source.
   */
  public static double[] getBaseline(String source) {
    if (!isSet(source)) {
      throw new IllegalArgumentException("Baseline not previously set for " + source);
    }
    return baselines.get(source);
  }

  /**
   * Returns whether or not the baseline has been computed for the given source.
   * 
   * @param source The name of a power source, such as a tower or lounge
   * @return true if the baseline data exists for that source; false otherwise.
   */
  public static boolean isSet(String source) {
    return baselines.containsKey(source);
  }

  /**
   * Sets the baseline data for the given source. If the given source already contains baseline
   * data, that data is overwritten.
   * 
   * @param source The name of the source (such as a lounge or tower) of the given data
   * @param data 24 values corresponding to the 24 hours in a day
   * 
   * @throws IllegalArgumentException if the passed data array does not contain exactly 24 elements
   */
  public static void setBaseline(String source, double[] data) {
    if (data.length != 24) {
      throw new IllegalArgumentException("24 data elements required, but received " + data.length);
    }
    baselines.put(source, data);
  }

}
